package org.liaohailong.lib

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import org.gradle.internal.impldep.org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream

/**
 * Describe:
 *
 *
 * @author liaohailong
 * Time: 2021/11/16 14:36
 */
class HelloWorldTransform : Transform() {
    override fun getName(): String = "HelloWorldTransform"

    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> =
        TransformManager.CONTENT_CLASS

    override fun getScopes(): MutableSet<in QualifiedContent.Scope> =
        TransformManager.SCOPE_FULL_PROJECT

    override fun isIncremental(): Boolean = false

    override fun transform(transformInvocation: TransformInvocation) {
        super.transform(transformInvocation)
        // 当前 gradle 运行环境上下文。可以拿到日志等级，当前 project 名称路径等信息
        val context = transformInvocation.context
        // 是否支持增量
        val incremental = transformInvocation.isIncremental
        // 配合增量的输入
        val secondaryInputs = transformInvocation.secondaryInputs
        // 引用输入。该输入处理后不需要输出
        val referencedInputs = transformInvocation.referencedInputs

        // 普通输入。通常用该输入处理，该输入处理完后需要输出到指定位置
        val inputs = transformInvocation.inputs
        // 用于提供输出的位置。注意：该输出会变成下个 Transform 的输入
        val outputProvider = transformInvocation.outputProvider
        // 演示 Transform
        transform(inputs, outputProvider)
    }

    private fun transform(
        inputs: MutableCollection<TransformInput>,
        output: TransformOutputProvider,
    ) {
        inputs.forEach {
            // 目录输入源。也就是源码
            val directoryInputs = it.directoryInputs
            // jar 包输入源
            val jarInputs = it.jarInputs
            // 处理 directoryInputs
            processDir(directoryInputs, output)
            // 处理 jar
            processJar(jarInputs, output)
        }
    }

    private fun processJar(inputs: MutableCollection<JarInput>, output: TransformOutputProvider) {
        inputs.forEach { input ->
            val src = input.file
            val dest = output.getContentLocation(
                input.name,
                input.contentTypes,
                input.scopes,
                Format.JAR
            )
//            FileUtils.copyFile(src, dest)
            JarFile(src).use { jf ->
                JarOutputStream(dest.outputStream()).use { jos ->
                    // 遍历 jar 文件中的内容
                    val entries = jf.entries()
                    while (entries.hasMoreElements()) {
                        val entry = entries.nextElement()
                        jf.getInputStream(entry).use { ips ->
                            val newEntry = JarEntry(entry.name)
                            // 可以在这里过滤掉不想处理的 entry
                            if (
                                entry.name.endsWith(".class").not()
                                || entry.name.startsWith("org/junit")
                            ) {
                                jos.putNextEntry(newEntry)
                                jos.write(ips.readBytes())
                            } else {
                                val cr = ClassReader(ips)
                                val cw = ClassWriter(cr, 0)
                                val cv = HelloWorldClassVisitor(cw)
                                cr.accept(cv, 0)
                                jos.putNextEntry(newEntry)
                                jos.write(cw.toByteArray())
                            }
                            jos.closeEntry()
                        }
                    }
                }
            }
        }
    }

    private fun processDir(
        inputs: MutableCollection<DirectoryInput>,
        output: TransformOutputProvider,
    ) {
        inputs.forEach { input ->
            val src = input.file
            val dest = output.getContentLocation(
                input.name,
                input.contentTypes,
                input.scopes,
                Format.DIRECTORY
            )
            FileUtils.copyDirectory(src, dest)
            dest.walk().forEach file@{ file ->
                if (!file.isFile || file.extension != "class") return@file
                file.inputStream().use { fis ->
                    val cr = ClassReader(fis)
                    val cw = ClassWriter(cr, 0)
                    val cv = HelloWorldClassVisitor(cw)
                    cr.accept(cv, 0)
                    file.outputStream().use { fos ->
                        fos.write(cw.toByteArray())
                    }
                }
            }
        }
    }
}